搭建演示项目 搭建 web 工程 创建 Maven war 类型的项目,创建 web.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns ="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation ="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version ="2.5" > <display-name > spring-security-01</display-name > <welcome-file-list > <welcome-file > index.jsp</welcome-file > </welcome-file-list > <servlet > <servlet-name > springDispatcherServlet</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <init-param > <param-name > contextConfigLocation</param-name > <param-value > classpath:spring.xml</param-value > </init-param > <load-on-startup > 1</load-on-startup > </servlet > <servlet-mapping > <servlet-name > springDispatcherServlet</servlet-name > <url-pattern > /</url-pattern > </servlet-mapping > </web-app >
pom 文件中引入相关依赖 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.atguigu.maven</groupId > <artifactId > SpringSucurity-HelloWord</artifactId > <version > 0.0.1-SNAPSHOT</version > <packaging > war</packaging > <dependencies > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-webmvc</artifactId > <version > 4.3.20.RELEASE</version > </dependency > <dependency > <groupId > javax.servlet.jsp</groupId > <artifactId > jsp-api</artifactId > <version > 2.2</version > <scope > provided</scope > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > servlet-api</artifactId > <version > 2.5</version > <scope > provided</scope > </dependency > <dependency > <groupId > javax.servlet</groupId > <artifactId > jstl</artifactId > <version > 1.2</version > </dependency > </dependencies > </project >
进行 spring 配置 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:context ="http://www.springframework.org/schema/context" xmlns:mvc ="http://www.springframework.org/schema/mvc" xsi:schemaLocation ="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd" > <context:component-scan base-package ="com.atguigu.security" > </context:component-scan > <bean class ="org.springframework.web.servlet.view.InternalResourceViewResolver" > <property name ="prefix" value ="/WEB-INF/views/" > </property > <property name ="suffix" value =".jsp" > </property > </bean > <mvc:annotation-driven /> <mvc:default-servlet-handler /> </beans >
引入页面和 controller 直接复制页面到 webapp 下面,复制 controller 包下面。
启动项目,访问首页 这时就能看到项目正常启动,且能正常访问了。
引入 SpringSecurity 在 pom 文件中添加下面依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <dependency > <groupId > org.springframework.security</groupId > <artifactId > spring-security-web</artifactId > <version > 4.2.10.RELEASE</version > </dependency > <dependency > <groupId > org.springframework.security</groupId > <artifactId > spring-security-config</artifactId > <version > 4.2.10.RELEASE</version > </dependency > <dependency > <groupId > org.springframework.security</groupId > <artifactId > spring-security-taglibs</artifactId > <version > 4.2.10.RELEASE</version > </dependency >
创建一个类让其继承自 WebSecurityConfigurerAdapter。
1 2 3 4 5 6 7 8 9 10 11 12 package com.atguigu.security.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration @EnableWebSecurity public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter {}
再次访问我们的页面,回发现我们的所有请求都被拦截了,并重定向到了一个默认的 login 登录页面。
实验1 - 授权首页和静态资源 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package com.atguigu.security.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration @EnableWebSecurity public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/layui/**" , "/index.jsp" ).permitAll().anyRequest().authenticated(); } }
这个时候:
如果访问 http://localhost:8080/SpringSucurity-HelloWord/ 或者 http://localhost:8080/SpringSucurity-HelloWord/index.jsp 或者 http://localhost:8080/SpringSucurity-HelloWord/layui/layui.js 都是可以访问的。
如果访问 layui 下不存在的资源,那么会提示 404 错误(有权限访问,但是资源不存在)。
但是随便访问一个不存在的地址,比如 http://localhost:8080/SpringSucurity-HelloWord/xx 那么就会提示 403 Forbidden
实验2 - 默认及自定义登录页 默认登录页 启用默认的登录页,让提示 403 的访问都去到默认的登录页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 package com.atguigu.security.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration @EnableWebSecurity public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/layui/**" , "/index.jsp" ).permitAll().anyRequest().authenticated(); http.formLogin(); } }
自定义登录页 可以指定单独的登录页面,并且可以指定账号参数名称和密码参数名称。可以指定登录请求处理地址。还可以禁用 csrf。
注意⚠️:默认的请求处理地址是 /login。账号参数名称为 username,密码参数名称为 password。请求方式是 POST,而且需要加上 csrf。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package com.atguigu.security.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration @EnableWebSecurity public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/layui/**" , "/index.jsp" ).permitAll().anyRequest().authenticated(); http.formLogin() .loginPage("/index.jsp" ) .usernameParameter("loginacct" ) .passwordParameter("userpswd" ) .loginProcessingUrl("/doLogin" ) .defaultSuccessUrl("/main.html" ); http.csrf().disable(); } }
可以在页面中使用 ${SPRING_SECURITY_LAST_EXCEPTION.message}
来获取错误信息
实验3 - 自定义认证 这里演示基于内存的认证方式。重写 configure 方法,参数是 AuthenticationManagerBuilder 类型的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package com.atguigu.security.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration @EnableWebSecurity public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication().withUser("zhangsan" ).password("123456" ).roles("学徒" ) .and() .withUser("lisisi" ).password("123" ).authorities("罗汉拳" , "吸星大法" ); } @Override protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/layui/**" , "/index.jsp" ).permitAll().anyRequest().authenticated(); http.formLogin() .loginPage("/index.jsp" ) .usernameParameter("loginacct" ) .passwordParameter("userpswd" ) .loginProcessingUrl("/doLogin" ) .defaultSuccessUrl("/main.html" ); http.csrf().disable(); } }
使用 zhangsan 和 lisisi 就能登录成功了。
所有的表单提交为了防止跨站请求伪造,我们需要加上隐藏域 _csrf
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
或者暂时禁用 http.csrf().disable();
上面的例子中我们是禁用的,现在我们可以不禁用,只要在表单中加上上面的隐藏 input 内容即可。
如果不禁用 csrf,默认是开启的状态,页面不设置 csrf 表单域,那么提交登录请求会报错
csrf token 值的变化:
如果登录成功(用户名,密码正确), 令牌会被删除。
重新回到登录页或后退网页, 令牌会重新生成
如果登录失败(用户名,密码错误), 令牌不变。
刷新登录页, 令牌值也不变
实验4 - 注销 在 configure 方法中可以开启注销,可以使用默认的或者自定义的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package com.atguigu.security.config;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;@Configuration @EnableWebSecurity public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure (HttpSecurity http) throws Exception { http.authorizeRequests().antMatchers("/layui/**" , "/index.jsp" ).permitAll().anyRequest().authenticated(); http.formLogin() .loginPage("/index.jsp" ) .usernameParameter("loginacct" ) .passwordParameter("userpswd" ) .loginProcessingUrl("/doLogin" ) .defaultSuccessUrl("/main.html" ); http.logout().logoutUrl("/doLogout" ).logoutSuccessUrl("/index.jsp" ); } }
页面中内容如下:
1 2 3 4 <form id ="logoutForm" action ="${PATH }/doLogout" method ="POST" > <input type ="hidden" name ="${_csrf.parameterName}" value ="${_csrf.token}" /> <a onclick ="$('#logoutForm').submit()" > 退出</a > </form >
实验5 - 基于角色的访问控制 我们可以设置 level1 下的所有资源只能 “学徒” 角色能访问,level2 下的资源只能 “大师” 能访问,level3 下的资源只能 “宗师” 能访问。其余的登录后都可以访问。
1 2 3 4 5 6 http.authorizeRequests().antMatchers("/layui/**" , "/index.jsp" ).permitAll() .antMatchers("/level1/**" ).hasRole("学徒" ) .antMatchers("/level2/**" ).hasRole("大师" ) .antMatchers("/level3/**" ).hasRole("宗师" ) .anyRequest().authenticated();
因为上面已经为 zhangsan 这个用户登录后赋予 “学徒” 的角色,所以 zhangsan 登录后就能访问 level1 下的所有资源了。
1 2 auth.inMemoryAuthentication().withUser("zhangsan" ).password("123456" ).roles("学徒" ).and().withUser("lisisi" ).password("123" ).authorities("罗汉拳" , "吸星大法" );
访问效果如下所示:
实验6 - 自定义访问拒绝处理页面 让出现 403 的错误都跳转到 /unauth.html
1 2 http.exceptionHandling().accessDeniedPage("/unauth.html" );
controller 新增处理方法,并新建 unauth.html 页面
1 2 3 4 @GetMapping ("/unauth.html" )public String unauth () { return "unauth" ; }
此时再次访问无权限的地址,就会跳转到 unauth.html 页面了
实验7 - 记住我 基于 Cookie 开启 “记住我” 的功能
提交表单的时候需要提交 “remember-me” 参数
1 <input type ="checkbox" name ="remember-me" lay-skin ="primary" title ="记住密码" >
默认会记住2周登录状态,会在 cookie 中保存名为,remember-me 的 cookie。登录后页面,关闭浏览器,再次打开浏览器,直接访问就不需要再次登录了。这种方式,token值是放置在内存中的,服务器端重启 tomcat, token 会失效。需要将token记录在数据库持久化才不会失效。
基于数据库 引入依赖
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <dependency > <groupId > org.springframework</groupId > <artifactId > spring-orm</artifactId > <version > 4.3.20.RELEASE</version > </dependency > <dependency > <groupId > com.alibaba</groupId > <artifactId > druid</artifactId > <version > 1.1.12</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.47</version > </dependency >
配置数据源
1 2 3 4 5 6 7 8 9 10 11 12 <bean id ="dataSource" class ="com.alibaba.druid.pool.DruidDataSource" > <property name ="username" value ="root" > </property > <property name ="password" value ="root" > </property > <property name ="url" value ="jdbc:mysql://192.168.137.3:3306/security?useSSL=false" > </property > <property name ="driverClassName" value ="com.mysql.jdbc.Driver" > </property > </bean > <bean id ="jdbcTemplate" class ="org.springframework.jdbc.core.JdbcTemplate" > <property name ="dataSource" ref ="dataSource" > </property > </bean >
创建数据库表 (SQL 语句是框架中的)
1 create table persistent_logins (username varchar (64 ) not null , series varchar (64 ) primary key ,token varchar (64 ) not null , last_used timestamp not null )
加上以下代码就能实现基于数据库的记住我功能(前端同样也要传递 remember-me 参数)1 2 3 4 JdbcTokenRepositoryImpl ptr = new JdbcTokenRepositoryImpl(); ptr.setDataSource(dataSource); http.rememberMe().tokenRepository(ptr);
再次测试就会发现能实现记住我功能了,且服务器重启不影响记住我的功能。且会在刚刚创建的表里面看见了新插入的一条数据。
认证 密码加密进行校验 根据 security.sql 文件创建表
实现 UserDetailService 接口 loadUserByUsername(String username) 方法。并在里面进行用户查询,权限的查询。并将查询到的信息创建 User 对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 package com.atguigu.security.component;import java.util.Map;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.security.core.authority.AuthorityUtils;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Component;@Component public class AppUserDetailsServiceImpl implements UserDetailsService { @Autowired JdbcTemplate jdbcTemplate; @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { String queryUser = "SELECT * FROM `t_admin` WHERE loginacct=?" ; Map<String, Object> map = jdbcTemplate.queryForMap(queryUser, username); return new User(map.get("loginacct" ).toString(), map.get("userpswd" ).toString(), AuthorityUtils.createAuthorityList("ROLE_学徒" ,"ROLE_大师" , "ADMIN" )); } }
在重写的 configure 方法中启用自定义的查询认证方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 import javax.sql.DataSource;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;@Configuration @EnableWebSecurity public class AppWebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired DataSource dataSource; @Autowired UserDetailsService userDetailsService; @Override protected void configure (AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); } }
在数据中新增 zhangsan 的用户,设置密码为 123456。因为这里给予的权限是写死的 学徒 和 大师,所以访问现象和以前基于内存是一样的。
采用 MD5 加密 定义 AppPasswordEncoder 类,实现 PasswordEncoder 接口,并复写其中的两个方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.atguigu.security.component;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Component;@Component public class AppPasswordEncoder implements PasswordEncoder { @Override public String encode (CharSequence rawPassword) { return MD5Util.digest(rawPassword.toString()); } @Override public boolean matches (CharSequence rawPassword, String encodedPassword) { return encodedPassword.equals(encode(rawPassword)); } }
1 2 auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
将数据库中的 zhangsan 的密码改为 123456 其对应的 md5 值,测试登录后没问题。
采用 BCrypt 加密 采用 BCrypt 都不需要实现 PasswordEncoder 接口。直接传入一个 BCryptPasswordEncoder 对象即可。
1 2 auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
BCrypt 加密出来的数据每次都是不一样的,但其实原理就是虽然对同一个密码,每次生成的hash不一样,但是hash 中包含了 salt(hash产生过程:先随机生成salt,salt跟 password 进行 hash)在下次校验时,从hash中取出 salt,salt 跟 password 进行 hash。得到的结果跟保存在 DB 中的 hash 进行比对即可。
将数据库中的 zhangsan 的密码改为 123456 其对应的 BCrypt 值,测试登录后没问题。
细粒度的权限控制 上面的设计中,我们限制了 /level/1 下的所有菜单只要拥有学徒权限都可以访问,这还不是很细致。我们可以控制 controller 中每个方法的权限。在 AppWebSecurityConfig 类中加上 @EnableGlobalMethodSecurity(prePostEnabled = true)
注解,并去掉之前给每个 url 设置的权限信息
1 2 3 4 5 http.authorizeRequests().antMatchers("/layui/**" , "/index.jsp" ).permitAll() .anyRequest().authenticated();
@EnableGlobalMethodSecurity(prePostEnabled = true)
就是开启细粒度的全局方法级别的权限控制,即可以为每个 controller 中的方法设置权限信息。为 GongfuController 中不同的 url 地址设置不同的权限信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.PathVariable;@Controller public class GongfuController { @PreAuthorize ("hasRole('学徒') AND hasAuthority('luohan')" ) @GetMapping ("/level1/1" ) public String leve1Page1 () { return "/level1/1" ; } @PreAuthorize ("hasRole('学徒') AND hasAuthority('wudang')" ) @GetMapping ("/level1/2" ) public String leve1Page2 () { return "/level1/2" ; } @PreAuthorize ("hasRole('学徒') AND hasAuthority('quanzhen')" ) @GetMapping ("/level1/3" ) public String leve1Page3 () { return "/level1/3" ; } }
重写 UserDetailsService 中查询用户信息的方法(查询用户有什么权限,有什么角色,如果有对应 controller 方法上的角色和权限则可以访问该方法)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package com.atguigu.security.component;import java.util.HashSet;import java.util.List;import java.util.Map;import java.util.Set;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.ColumnMapRowMapper;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;@Component public class AppUserDetailsServiceImpl implements UserDetailsService { @Autowired JdbcTemplate jdbcTemplate; @Override public UserDetails loadUserByUsername (String username) throws UsernameNotFoundException { String sql = "select * from t_admin where loginacct=?" ; Map<String, Object> map = jdbcTemplate.queryForMap(sql, username); String sql1 = "SELECT t_role.* FROM t_role LEFT JOIN t_admin_role ON t_admin_role.roleid=t_role.id WHERE t_admin_role.adminid=?" ; List<Map<String, Object>> roleList = jdbcTemplate.query(sql1, new ColumnMapRowMapper(), map.get("id" )); String sql2 = "SELECT distinct t_permission.* FROM t_permission LEFT JOIN t_role_permission ON t_role_permission.permissionid = t_permission.id LEFT JOIN t_admin_role ON t_admin_role.roleid=t_role_permission.roleid WHERE t_admin_role.adminid=?" ; List<Map<String, Object>> permissionList = jdbcTemplate.query(sql2, new ColumnMapRowMapper(), map.get("id" )); Set<GrantedAuthority> authorities = new HashSet<GrantedAuthority>(); for (Map<String, Object> rolemap : roleList) { String rolename = rolemap.get("name" ).toString(); authorities.add(new SimpleGrantedAuthority("ROLE_" + rolename)); } for (Map<String, Object> permissionmap : permissionList) { String permissionName = permissionmap.get("name" ).toString(); if (!StringUtils.isEmpty(permissionName)) { authorities.add(new SimpleGrantedAuthority(permissionName)); } } return new User(map.get("loginacct" ).toString(), map.get("userpswd" ).toString(), authorities); } }
为 zhangsan 赋予赋予学徒的角色,并为学徒角色赋予 luohan 的权限。
将角色赋予罗汉拳的权限
此时再次访问系统,因为 zhangsan 只有学徒的角色和罗汉拳的权限,所以能访问罗汉拳的页面。
注意⚠️: